package prefuse.data.io;

import java.awt.List;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import prefuse.data.Graph;
import prefuse.data.Node;
import prefuse.data.Edge;
import prefuse.data.Table;
import prefuse.data.Tree;
import prefuse.data.parser.DataParseException;
import prefuse.data.parser.DataParser;
import prefuse.data.parser.ParserFactory;


/**
 * GraphReader instance that reads in tree-structured data in the
 * XML-based TreeML format. TreeML is an XML format originally created for
 * the 2003 InfoVis conference contest. A DTD (Document Type Definition) for
 * TreeML is
 * <a href="http://www.nomencurator.org/InfoVis2003/download/treeml.dtd">
 *  available online</a>.
 * 
 * @author <a href="http://jheer.org">jeffrey heer</a>
 */
public class TreeMLReader extends AbstractGraphReader {

    private ParserFactory m_pf = ParserFactory.getDefaultFactory();
    public static String[]  quaryArray = new String[]{};
    public static ArrayList<Float> arrayForAllValue = new ArrayList<Float>();
    public static  Set<Double> NodesTimeList = new HashSet<Double>();
    /**
     * @see prefuse.data.io.GraphReader#readGraph(java.io.InputStream)
     */
    public Graph readGraph(InputStream is) throws DataIOException {
        try {       
            TreeMLHandler    handler   = new TreeMLHandler();
            SAXParserFactory factory   = SAXParserFactory.newInstance();
            SAXParser        saxParser = factory.newSAXParser();
            saxParser.parse(is, handler);
            return handler.getTree();
        } catch ( Exception e ) {
            throw new DataIOException(e);
        }
    }

    /**
     * String tokens used in the TreeML format.
     */
    public static interface Tokens {
        public static final String TREE   = "DecisionTree";
        public static final String ChildList = "childList";
        public static final String LEAF   = "leaf";
        public static final String ATTR   = "attribute";
        public static final String NAME   = "name";
        public static final String VALUE  = "value";
        public static final String TYPE   = "type";
        public static final String TREENODETYPE   = "TreeNode";
        
        //for tree node information
        public static final String TreeNodeID   = "treeNodeID";
        public static final String time   = "time";
        public static final String variableName   = "variableName";
        public static final String terminalValue   = "terminalValue";
        public static final String rValue   = "rollBackValue";
        public static final String tValue   = "terminalValue";
        
        public static final String Selected   = "selected";
        public static final String idOfBestEdge   = "idOfBestDecision";
        public static final String sourceEdgeID   = "sourceEdgeID";
        
        
        public static final String ChildEdgeList   = "childrenEdgeList";
        public static final String Edge   = "Edge";
        public static final String EdgeId   = "edgeID";
        public static final String eName   = "edgeName";
        public static final String eValue   = "edgeValue";
        public static final String eProbability   = "probability";
        
        public static final String DECLS  = "declarations";
        public static final String DECL   = "attributeDecl";
        
        public static final String INT = "Int";
        public static final String INTEGER = "Integer";
        public static final String LONG = "Long";
        public static final String FLOAT = "Float";
        public static final String REAL = "Real";
        public static final String STRING = "String";
        public static final String DATE = "Date";
        public static final String CATEGORY = "Category";
        
        // prefuse-specific allowed types
        public static final String BOOLEAN = "Boolean";
        public static final String DOUBLE = "Double";
     
    
    
    }
    
     
    
    /**
     * A SAX Parser for TreeML data files.
     */
    public class TreeMLHandler extends DefaultHandler implements Tokens {
        
        private Table m_nodes = null;
        private Table m_edges = null;
        private Tree m_tree = null;
        
        private Node m_activeNode = null;
        private Edge m_activeEdge = null;
       
        private boolean m_inSchema = true;
        
    	public int treeNodeId;
    	public String treeNodeIdByString;
    	public String treeNodeTimeByString;
    	public String treeNodeVariableName;
    	public String treeNoderValue;
    	public String treeNodetValue;
    	public String treeNodeBeSelected;
    	public String BestDecisionByString;
    	
    	public boolean hasNodeId = false;
    	public boolean hasTime = false;
    	public boolean hasVariableName = false;
     	public boolean hasrValue = false;
     	public boolean hastValue = false;
    	public boolean hasSelected = false;
    	public boolean isDecisionNode=false;
    	public boolean isTerminalNode=false;  	
    	public boolean hasBestEdge=false;
    	
    	public int treeEdgeId;
    	public String treeEdgeIdByString;
    	public String treeEdgeNameByString;
    	public String treeEdgeValueByString;
    	public String treeEdgeProByString;
    	public int BestDecisionEdgeId;
    	
    	
    	public boolean hasEdgeId = false;
    	public boolean hasEdgeName = false;
    	public boolean hasEdgeValue = false;
    	public boolean hasEdgePro = false;
    	
    	public int childrenEdgeNumber = 0;
    	
 //   	public String quaryArray[]= new String[] {}; ;
    	
        public void startDocument() {
            m_tree = new Tree();
            m_nodes = m_tree.getNodeTable();
            m_edges = m_tree.getEdgeTable();
        }
        
        private void schemaCheck() {
            if ( m_inSchema ) {
                m_inSchema = false;
            }
        }
        
        public void endElement(String namespaceURI, String localName, String qName) {
            if ( qName.equals(ChildList) ) {
           
            }
          else 
        	  if(qName.equals(TREENODETYPE) ){  //if the tag = TreeNode
        	  m_activeNode = m_activeNode.getParent();
        	 	isTerminalNode = false;
        }
//            else if(m_activeNode!=null&&qName.equals(TREENODETYPE) ){
//            	m_activeNode = m_activeNode.getParent();
//            }
        	  
        	  else if ( qName.equals(TreeNodeID) ) {   //TreeNodeID   = "treeNodeID"       		
        		  hasNodeId = false;      
        	  }
        	  else if ( qName.equals(time) ) {   //TreeNodeID   = "treeNodeID"       		
        		  hasTime = false;      
        	  }
        	  else if ( qName.equals(variableName) ) {   //TreeNodeID   = "treeNodeID"       		
        		  hasVariableName = false;      
        	  }
        	  else if ( qName.equals(rValue) ) {   //TreeNodeID   = "treeNodeID"       		
        		  hasrValue = false;      
        	  }
        	  else if ( qName.equals(tValue) ) {   //TreeNodeID   = "treeNodeID"       		
        		  hastValue = false;      
        	  }
        	  else if(qName.equals(Selected)){
        		  hasSelected = false;
        	  }
        	  else if ( qName.equals(EdgeId) ) {        		
      			hasEdgeId = false;
            		
          	}
        	  else if ( qName.equals(eName) ) {        		
        		  hasEdgeName = false;
              		
            	}
        	  else if ( qName.equals(eValue) ) {        		
        			hasEdgeValue = false;
              		
            	}
        	  else if ( qName.equals(eProbability) ) {        		
        			hasEdgePro = false;
              		
            	}
        	  else if ( qName.equals(idOfBestEdge) ) {        		
        		  hasBestEdge = false;
              		
            	}
        	 
        }
        
        
        public void startElement(String namespaceURI, String localName,
                                 String qName, Attributes atts) { 
        	
        	
//        	   Class t = parseType("String");
 //              m_nodes.addColumn("name", t);
//            if ( qName.equals(DECL) ) {
//                if ( !m_inSchema ) {
//                    throw new RuntimeException("All declarations must be done "
//                            + "before nodes begin");
//                }
//                String name = atts.getValue(NAME);
//                String type = atts.getValue(TYPE);
//                Class t = parseType(type);
//                m_nodes.addColumn(name, t);
//            }
//            else 
        	if(qName.equals(TREE)&&!(atts.getLength()>0) ){ //TREE   = "DecisionTree"
     
        		 Class t = parseType("String");
                 m_nodes.addColumn("name", t);
                 t = parseType("Int");
                 m_nodes.addColumn("id", t);
                 t = parseType("Double");
                 m_nodes.addColumn("time", t);
                 t = parseType("String");
                 m_nodes.addColumn("variableName", t);
                 t = parseType("FLOAT");
                 m_nodes.addColumn("rollBackValue", t);
                 t = parseType("FLOAT");
                 m_nodes.addColumn("terminalValue", t);
                 t = parseType("BOOLEAN");
                 m_nodes.addColumn("Selected", t);
                 t = parseType("Int");
                 m_nodes.addColumn("idOfBestDecision", t);
                 
                 Class EdgeTypeClass = parseType("String");//Deal with BasicEdge
                 m_edges.addColumn("edgeType", EdgeTypeClass);
                 EdgeTypeClass = parseType("Int");
                 m_edges.addColumn("edgeId", EdgeTypeClass);
                 EdgeTypeClass = parseType("String");
                 m_edges.addColumn("edgeName", EdgeTypeClass);
                 EdgeTypeClass = parseType("DOUBLE");
                 m_edges.addColumn("edgeValue", EdgeTypeClass);
                 EdgeTypeClass = parseType("DOUBLE");
                 m_edges.addColumn("probability", EdgeTypeClass);
//                 EdgeTypeClass = parseType("BOOLEAN");
//                 m_edges.addColumn("edgeSelected", EdgeTypeClass);
//                 EdgeTypeClass = parseType("Int");
//                 m_edges.addColumn("edgeSourceId", EdgeTypeClass);
//                 EdgeTypeClass = parseType("Int");
//                 m_edges.addColumn("edgeDestinationId", EdgeTypeClass);
                 
                 
                 
        	}
        	
        	if ( qName.equals(TreeNodeID) ) {        		
        			hasNodeId = true;
              		
        	}
        	else if ( qName.equals(time) ) {        		
    			hasTime = true;
          		
        	}
        	else if ( qName.equals(variableName) ) {        		
    			hasVariableName = true;
          		
        	}
        	else if ( qName.equals(rValue)&&!isTerminalNode) {        		
    			hasrValue = true;
          		
        	}
        	else if ( qName.equals(tValue)&&isTerminalNode) {        		
    			hastValue = true;
          		
        	}
        	else if(qName.equals(Selected)&&isDecisionNode){
        			hasSelected = true; //Because in the childrenEdgeList there is <selected> too, don't want to mass them
        	}
        	
        	else if ( qName.equals(EdgeId) ) {        		
    			hasEdgeId = true;
          		
        	}
        	else if ( qName.equals(eName) ) {        		
        		hasEdgeName = true;
          		
        	}
        	else if ( qName.equals(eValue) ) {        		
        		hasEdgeValue = true;
          		
        	}
        	else if ( qName.equals(eProbability) ) {        		
        		hasEdgePro = true;
          		
        	}
        	else if( qName.equals(idOfBestEdge) ){
        		hasBestEdge = true;
        	}
        	
//            	if ( qName.equals(ChildList) ) {
//            	
//                schemaCheck();
//                
//                // parse a node element
//                Node n;
//                if ( m_activeNode == null ) {
//                    n = m_tree.addRoot();
//                } else {
//                    n = m_tree.addChild(m_activeNode);
//                }
//                m_activeNode = n;
//            }
//            	else 
//            		if(qName.equals(TREE)&&(atts.getLength()>0)){
//            		
//            			 schemaCheck();
//            			 System.out.println("i am in qName.equals(TREE)&&(atts.getLength()>0)");
//                         // parse a node element
//                         Node n;
//                         if ( m_activeNode == null ) {
//                             n = m_tree.addRoot();
//                         } else {
//                             n = m_tree.addChild(m_activeNode);
//                         }
//                         m_activeNode = n;
//                        
//               
//            	}
//            else 
            	if ( qName.equals(TREENODETYPE) ) { //if the tag = TreeNode
            	
	                schemaCheck();                
	                // parse a node element
	                Node n;
	                if ( m_activeNode == null ) {
	                    n = m_tree.addRoot();
	                } else {
	                    n = m_tree.addChild(m_activeNode);
	                  
	                }
	                m_activeNode = n;                     
	                parseAttribute(atts);
            }
            else if ( qName.equals(ChildEdgeList) ) {
            	isDecisionNode = false;         
            }
            else if(qName.equals(Edge)){
            	childrenEdgeNumber = childrenEdgeNumber++;     
            	m_activeEdge = m_tree.addEdgeWithoutTarget(m_activeNode.getRow());//set the target Node = 0    
            	parseAttributeForEdges(atts);
            }
            	
            	
        }
        
        public void characters(char ch[], int start, int length) throws SAXException {
			
			
			if (hasNodeId) {		
				treeNodeIdByString = new String(ch, start, length);
//				treeNodeId = Integer.parseInt(treeNodeIdByString);
		
				parseAttributeWithoutFormat(treeNodeIdByString,"id");
				
			}
			if (hasTime) {		
				treeNodeTimeByString = new String(ch, start, length);
//				treeNodeId = Integer.parseInt(treeNodeIdByString);
				 double currentTime = Double.parseDouble(treeNodeTimeByString);
				 NodesTimeList.add(currentTime);
				parseAttributeWithoutFormat(treeNodeTimeByString,"time");
				
			}
			if (hasVariableName) {		
				treeNodeVariableName = new String(ch, start, length);
//				treeNodeId = Integer.parseInt(treeNodeIdByString);
		
				parseAttributeWithoutFormat(treeNodeVariableName,"variableName");
				
			}
			if (hasrValue&&!isTerminalNode) {
				
				treeNoderValue = new String(ch, start, length);	
				parseAttributeWithoutFormat(treeNoderValue,"rollBackValue");
				
			}
			if (hastValue&&isTerminalNode) {
				
				treeNodetValue = new String(ch, start, length);	
				parseAttributeWithoutFormat(treeNodetValue,"terminalValue");
				
			}
			if(hasSelected){
			//	System.out.println("hasSelected  : " + hasSelected + "hasNodeId  "+ hasNodeId);
				treeNodeBeSelected = new String(ch, start, length);
			//	System.out.println("selected  : " + treeNodeBeSelected);
				
				parseAttributeWithoutFormat(treeNodeBeSelected,"Selected");
				
			}
			if(hasEdgeId){
				treeEdgeIdByString = new String(ch, start, length);
	
		
				parseAttributeWithoutFormatForEdge(treeEdgeIdByString,"edgeId");
					
				}
			if(hasEdgeName){
				treeEdgeNameByString = new String(ch, start, length);
	
		
				parseAttributeWithoutFormatForEdge(treeEdgeNameByString,"edgeName");
					
				}
			if(hasEdgeValue){
				treeEdgeValueByString = new String(ch, start, length);
	
		
				parseAttributeWithoutFormatForEdge(treeEdgeValueByString,"edgeValue");
					
				}
			if(hasEdgePro){
				treeEdgeProByString = new String(ch, start, length);
	
		
				parseAttributeWithoutFormatForEdge(treeEdgeProByString,"probability");
					
				}
			if(hasBestEdge){
				BestDecisionByString = new String(ch, start, length);
				//save every BestDecision Edge Id into quaryArray
				//Writen by Wenyi AN
				if(quaryArray==null){
					quaryArray[0] = BestDecisionByString;
				}
				else{
					quaryArray=Arrays.copyOf(quaryArray, quaryArray.length+1);
					quaryArray[quaryArray.length-1]=BestDecisionByString;
//					for (String s : quaryArray) {  
//						System.out.println("fffffffffffffffffffff  : " + s);}
				}
				
				
				parseAttributeWithoutFormat(BestDecisionByString,"idOfBestDecision");
				}
	 
		}
        
       
        
        protected void parseAttribute(Attributes atts) {
            String alName, name = "name", value = null;
            for ( int i = 0; i < atts.getLength(); i++ ) {
                alName = atts.getQName(i);
        //        System.out.println(" "+i);
               if ( alName.equals(TYPE) ) {
                	value = atts.getValue(i);
                }
            }

            try {
            	if(value.equals("DecisionNode")){
            		isDecisionNode = true;           		
            	}
            	if(value.equals("TerminalNode")){
            		System.out.println("this is terminal node");
            		isTerminalNode = true;           		
            	}
                Object val = parse(value, m_nodes.getColumnType(name));                
                m_activeNode.set(name, val);
            } catch ( Exception e ) {
                throw new RuntimeException("I am in parseAttribute");
            }
        }
        
        protected void parseAttributeForEdges(Attributes atts) {
            String alName, name = "edgeType", value = null;
            for ( int i = 0; i < atts.getLength(); i++ ) {
                alName = atts.getQName(i);
               if ( alName.equals(TYPE) ) {
                	value = atts.getValue(i);
                }
            }

            try {
            	 
                Object val = parse(value, m_edges.getColumnType(name)); 
         //       System.out.println("I am here!");
                m_activeEdge.set(name, val);
            } catch ( Exception e ) {
                throw new RuntimeException("I am in parseAttributeForEdges");
            }
        }
        
        protected void parseAttributeWithoutFormat(String treeNodeInfo,String name) {
          //  String alName, name = "id";           

            try {
                Object val = parse(treeNodeInfo, m_nodes.getColumnType(name));
                m_activeNode.set(name, val);
                if(name.equals("terminalValue"))
                {
                	float nodeTValue = m_activeNode.getFloat(terminalValue);
                	arrayForAllValue.add(nodeTValue);
                }
                if(name.equals("rollBackValue"))
                {
                	float nodeRValue = m_activeNode.getFloat(rValue);
                	System.out.println("nodeRValue ****" + nodeRValue);
                	arrayForAllValue.add(nodeRValue);
                }
            } catch ( Exception e ) {
                throw new RuntimeException("I am in parseAttributeWithoutFormat");
            }
        }
        
        
        protected void parseAttributeWithoutFormatForEdge(String treeEdgeInfo,String name) {
            //  String alName, name = "id";           

              try {
                  Object val = parse(treeEdgeInfo, m_edges.getColumnType(name));
                  m_activeEdge.set(name, val);
              } catch ( Exception e ) {
                  throw new RuntimeException("I am in parseAttributeWithoutFormatForEdge");
              }
          }
        protected Object parse(String s, Class type)
            throws DataParseException
        {
            DataParser dp = m_pf.getParser(type);
    //        System.out.println("the is in function parse  " + s);
            return dp.parse(s);
        }
        
        protected Class parseType(String type) {
            type = Character.toUpperCase(type.charAt(0)) +
                   type.substring(1).toLowerCase();
            if ( type.equals(INT) || type.equals(INTEGER) ) {
                return int.class;
            } else if ( type.equals(LONG) ) {
                return long.class;
            } else if ( type.equals(FLOAT) ) {
                return float.class;
            } else if ( type.equals(DOUBLE) || type.equals(REAL)) {
                return double.class;
            } else if ( type.equals(BOOLEAN) ) {
                return boolean.class;
            } else if ( type.equals(STRING) ) {
                return String.class;
            } else if ( type.equals(DATE) ) {
                return Date.class;
            } else {
                throw new RuntimeException("Unrecognized data type: "+type);
            }
        }
        
        public Tree getTree() {
            return m_tree;
        }
        
      
    } // end of inner class TreeMLHandler
    
} // end of class TreeMLTReeReader
